import { LoadingManager, VideoTexture } from "three";
import { AbstractAssetTaskInfo, AudioTaskInfo, CubeTextureTaskInfo, DataTaskInfo, GLTFMeshTaskInfo, HDRCubeTextureTaskInfo, TASK_INFO_TYPES, TextureTaskInfo, VideoTextureTaskInfo } from "./asset-task-info";
import { ILoose } from "../../babylon-lib";


/**
 * 资源加载（解析）
 * @param scene 所在的场景
 */
class AssetsLoad {
    public readonly taskInfos: Array<AbstractAssetTaskInfo<any, any>>;

    private _manager!: LoadingManager;
    private _progress = 0;
    private _total = Number.POSITIVE_INFINITY;
    private _onStart: { (): void } | undefined;
    private _onProgress: { (progress: number): void } | undefined;
    private _onTaskSuccess: { (task: any): void } | undefined;
    private _onFinish: { (): void } | undefined;
    private _onTaskError: { (task: any): void } | undefined;
    private _loadedTasks: Array<AbstractAssetTaskInfo<any, any>> = [];

    constructor() {
        this._manager = new LoadingManager();
        this.taskInfos = [];

        const mngr = this._manager;
        mngr.onProgress = (url: string, loaded: number, total: number): void => {
            const task = this.getTaskInfo(url);
            if (!task) return;
            if (this._loadedTasks.includes(task)) return;
            this._loadedTasks.push(task);
            this._progress += 0.9;
            this.processTask(url);
            if (this._onTaskSuccess) this._onTaskSuccess(url);
            if (this._onProgress) this._onProgress(this.progress);
        };
        mngr.onLoad = (): void => {
            for (const taskInfo of this.taskInfos) {
                if (taskInfo.data) continue;
                this.processTask(taskInfo.rawInfo);
            }
            this.checkLoadedAndResolved();
        };
        mngr.onError = (url: string): void => {
            if (this._onTaskError) this._onTaskError(url);
        };
    }

    /**
     * 获取加载进度（按加载资源数量计的）
     */
    public get progress(): number {
        return this._progress / this._total;
    }

    public getTaskInfo(rawUrl: string): AbstractAssetTaskInfo<any, any> | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.verify(rawUrl)) return taskInfo;
        }
        return null;
    }

    /**
     * 获取模型资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getMeshTaskInfo(rawUrl: string): GLTFMeshTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.GLTF_MESH) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as GLTFMeshTaskInfo;
        }
        return null;
    }

    /**
     * 获纹理型资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getTextureTaskInfo(rawUrl: string): TextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.TEXTURE) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as TextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取六面立方体纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getCubeTextureTaskInfo(rawUrl: string): CubeTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.CUBE_TEXTURE) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as CubeTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取高动态范围立方体纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getHDRCubeTextureTaskInfo(rawUrl: string): HDRCubeTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.HDR_CUBE_TEXTURE) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as HDRCubeTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取音频资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getAudioTaskInfo(rawUrl: string): AudioTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.AUDIO) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as AudioTaskInfo;
        }
        return null;
    }

    /**
     * 获取视频纹理资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getVideoTextureTaskInfo(rawUrl: string): VideoTextureTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.VIDEO) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as VideoTextureTaskInfo;
        }
        return null;
    }

    /**
     * 获取数据资源
     * @param rawUrl 资源原始路径
     * @returns 资源（含资源信息及资源）
     */
    public getDataTaskInfo(rawUrl: string): DataTaskInfo | null {
        for (const taskInfo of this.taskInfos) {
            if (taskInfo.type !== TASK_INFO_TYPES.DATA) continue;
            if (taskInfo.verify(rawUrl)) return taskInfo as DataTaskInfo;
        }
        return null;
    }

    /**
     * 加载资源
     * @param taskInfos 资源任务信息
     * @param onStart 开始加载回调函数
     * @param onProgress 加载中回调函数
     * @param onTaskSuccess 单个任务完成回调函数
     * @param onFinish 加载完成回调函数
     * @param onTaskError 异常回调函数
     */
    public load(taskInfos: ILoose<AbstractAssetTaskInfo<any, any>> | Array<AbstractAssetTaskInfo<any, any>>,
        onStart?: { (): void },
        onProgress?: { (progress: number): void },
        onTaskSuccess?: { (task: string): void },
        onFinish?: { (): void },
        onTaskError?: { (task: string): void },
    ): void {
        this._onStart = onStart;
        this._onProgress = onProgress;
        this._onTaskSuccess = onTaskSuccess;
        this._onFinish = onFinish;
        this._onTaskError = onTaskError;

        this.taskInfos.length = 0;
        this._loadedTasks.length = 0;
        const manager = this._manager;
        if (Array.isArray(taskInfos)) {
            for (let taskInfo of taskInfos) {
                this.taskInfos.push(taskInfo);
            }
        } else {
            for (let key in taskInfos) {
                const taskInfo = taskInfos[key];
                this.taskInfos.push(taskInfo);
            }
        }

        this._total = this.taskInfos.length;
        this._progress = 0;
        if (this._onStart) this._onStart();

        for (const taskInfo of this.taskInfos) {
            taskInfo.start(manager);
        }
    }

    private processTask(url: string): void {
        const targetInfo = this.getTaskInfo(url)!;
        switch (targetInfo.type) {
            case TASK_INFO_TYPES.GLTF_MESH:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.TEXTURE:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.CUBE_TEXTURE:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.HDR_CUBE_TEXTURE:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.AUDIO:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.DATA:
                this._progress += 0.1;
                break;
            case TASK_INFO_TYPES.VIDEO:
                this.generateVideoTexture(targetInfo as VideoTextureTaskInfo);
                break;
        }

    }

    /**
     * 创建视频纹理对象
     * @param info 任务信息
     * @returns 视频纹理
     */
    private generateVideoTexture(task: VideoTextureTaskInfo): void {
        const video = document.createElement('video');
        document.body.appendChild(video);
        video.load();
        video.src = task.rawInfo;
        video.onloadeddata = () => {
            const videoTexture = new VideoTexture(video);
            this._progress += 1;
            task.data = videoTexture;
            this.checkLoadedAndResolved();
            document.body.removeChild(video);
        };
    }

    /**
     * 检查资源加载和创建是否完成，并在完成时执行设置的回调函数
     */
    private checkLoadedAndResolved() {
        const ref = this._total - 0.001;
        if (this._progress < ref) return;
        if (this._onFinish) this._onFinish();
    }

}

export { AssetsLoad };